/*
 * Money Well - cool dragging stuff
 *
 * Author: Kris Younger, NeXT Systems Engineering
 * You may freely copy, distribute and reuse the code in this example.  
 * NeXT disclaims any warranty of any kind, expressed or implied, as to 
 * its fitness for any particular use.
 */

#import "MoneyWell.h"
#import "Transaction.h"
#import "MainDelegate.h"
#import "LedgerController.h"

#define BOX(X,Y,W,H) PSmoveto(X,Y); PSrlineto(0, H); PSrlineto (W, 0); \
		PSrlineto(0, (-1 * H)); PSclosepath()

@implementation MoneyWell

#define TRANS_PBOARDTYPE_NAME  "Transaction"

NXAtom TransPboardType = NULL;
static id           dragObject = nil;
static id           dragPBoard = nil;


- initFrame:(const NXRect *)frameRect
{
    NXAtom              type = NXUniqueString(TRANS_PBOARDTYPE_NAME);

    [super initFrame:frameRect];
    image = [[NXImage alloc] initFromSection:"well"];
    TransPboardType = type;
    [self registerForDraggedTypes:&type count:1];
    sel = NO;
    return self;
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (BOOL)acceptsFirstMouse
{
    return YES;
}

- free
{
    [self unregisterDraggedTypes];
    return[super free];
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    NXSize              imsze;
    NXPoint             origin;
    NXRect              inbounds;

    NXDrawButton(&bounds, rects);
    if (sel) {
	PSsetgray(1.0);
	BOX((bounds.origin.x + 1), (bounds.origin.y + 2),
	    (bounds.size.width - 4), (bounds.size.height - 3));
	PSfill();
    }
    inbounds.origin.x = inbounds.origin.y = 6;
    inbounds.size.width = bounds.size.width - 12;
    inbounds.size.height = bounds.size.height - 12;
    NXDrawGrayBezel(&inbounds, rects);
    [image getSize:&imsze];
    origin.x = (bounds.size.width / 2) - (imsze.width / 2);
    origin.y = (bounds.size.height / 2) - (imsze.height / 2);
    [image composite:NX_SOVER toPoint:&origin];

    return self;
}

#define mask (NX_LMOUSEUPMASK|NX_LMOUSEDRAGGEDMASK)
#define Shift(e) (e->flags&(NX_NEXTLSHIFTKEYMASK|NX_NEXTRSHIFTKEYMASK))
#ifndef abs
#define abs(x) (((x)<0)? -(x) : (x))
#endif

int
aMouseMoved(NXPoint *o, int n)
{				/* true if mouse moves > n pixels from 'o' */
    NXEvent            *e;
    NXPoint             p;
    float               d;

    do {
	e = [NXApp getNextEvent:mask];
	p = e->location;
	d = abs(p.x - o->x);
	if (d < abs(p.y - o->y))
	    d = abs(p.y - o->y);
    } while (e->type != NX_LMOUSEUP && d < n);
    *o = p;
    return e->type != NX_LMOUSEUP;
}

#define COPYPOINT(A, B) A.x = B.x; A.y = B.y
- mouseDown:(NXEvent *)e
{
    NXPoint             hit, me;
    int                 oldMask;
    NXEvent             saveEvent;
    Pasteboard         *dragPasteboard;

    oldMask = [window eventMask];
    [window setEventMask:(oldMask | mask)];
    saveEvent = *e;
    hit = e->location;
    COPYPOINT(me, hit);
    [self convertPoint:&me fromView:nil];

    if (((me.x <= 7.0) || (me.x >= (bounds.size.width - 7.0))) ||
	((me.y <= 7.0) || (me.y >= (bounds.size.height - 7.0)))) {
	if (sel) {
	    sel = NO;
	    [self display];
	} else {
	    sel = YES;
	    [self display];
	}
    } else {
	if (aMouseMoved(&hit, 4)) {
	    NXRect              dragRect;

	    [self convertPoint:&hit fromView:nil];
	    [self getBounds:&dragRect];
	    dragRect.origin.x = 8;
	    dragRect.origin.y = 8;
	    dragPasteboard = [Pasteboard newName:NXDragPboard];
	    [dragPasteboard declareTypes:&TransPboardType num:1 owner:self];
	    dragObject = nil;

	    [self dragImage:image at:&(dragRect.origin) offset :&hit
	     event:&saveEvent pasteboard:dragPasteboard
	     source:self slideBack:YES];
	}
    }
    return[super mouseDown:e];
}

- transaction:requestor
{
    id                  farOne, nearOne;
    double              trValue;
    const char         *today;
    char                buf[256];

    trValue = [valueTF doubleValue];
    if (trValue > 0.0) {
	nearOne = [[Transaction alloc] init];
	farOne = [[Transaction alloc] init];
	today = [[NXApp delegate] todaysDate];

	[nearOne setTAmount:-trValue];
	[farOne setTAmount:trValue];
	[nearOne setTDate:today];
	[farOne setTDate:today];
	strcpy(buf, "Transfer to ");
	strcat(buf,[requestor accountName]);
	[nearOne setTMemo:buf];
	strcpy(buf, "Transfer from ");
	strcat(buf,[ledgerController accountName]);
	[farOne setTMemo:buf];

	[ledgerController appendThis:nearOne];
	[ledgerController commit:self];
	[valueTF setStringValue:"0.00"];
	return farOne;
    } else
	return nil;
}

//Drag Support
- (NXDragOperation) draggingSourceOperationMaskForLocal:(BOOL)flag
{
    return (flag) ? NX_DragOperationCopy : NX_DragOperationNone;
}

static BOOL 
includesType(const NXAtom * types, NXAtom type)
{
    if (types)
	while (*types)
	    if (*types++ == type)
		return YES;
    return NO;
}

- (NXDragOperation) draggingEntered:(id <NXDraggingInfo >)sender
{
    char	*transName;
    const char	*const * types;
    int		length;
    id	aPasteboard;

    aPasteboard = [sender draggingPasteboard];
    for (types = [aPasteboard types]; *types; types++)
	if (*types == TransPboardType)
	{
	    [aPasteboard readType:*types data:&transName length:&length];
	    dragObject = [sender draggingSource];
	    [aPasteboard deallocatePasteboardData:transName length:length];
	    break;
	}

    return (dragObject != nil) ? NX_DragOperationCopy : NX_DragOperationNone;
}

- declareTypesFor:anObject inPasteboard:aPasteboard
{
    const char         *types[1] = {TransPboardType};

    if ([anObject isKindOfClassNamed:NXUniqueString(TRANS_PBOARDTYPE_NAME)]) {
	[aPasteboard declareTypes:types num:1 owner:self];
	dragObject = anObject;
	dragPBoard = aPasteboard;
	return self;
    }
    return nil;
}

- draggingExited:sender
{
    if (includesType([[sender draggingPasteboard] types], TransPboardType)) {
	dropping = NO;
	dragObject = nil;
	[self display];
    }
    return self;
}

- (BOOL)performDragOperation:(id <NXDraggingInfo >)sender
{
    Pasteboard         *pboard = [sender draggingPasteboard];

    dropping = NO;
    if (includesType([pboard types], TransPboardType)) {
	[ledgerController appendThis: [dragObject transaction:ledgerController]];
	[ledgerController commit:self];
	[valueTF setStringValue:"0.00"];
	return YES;
    } else
	return NO;
}

@end
